Published on

在SQL数据库中持久化复杂实体的方法

Authors
  • avatar
    Name
    Qiu Yuzhou
    Twitter

简单业务场景

在处理简单的业务逻辑时,通常可以通过执行CRUD操作来满足需求。 这种情况下,传统的SQL数据库表设计方法是将每个模型字段映射到数据库表字段。 这种做法在以下情况下尤其常见:

  • 使用active record模式的开发框架时
  • 直接使用ORM框架而不是进行模型分层时

复杂业务场景

面临的新挑战

在需要应用领域驱动设计的复杂业务场景中,单一实体模型可能变得异常复杂。使用传统方法可能会遇到以下挑战:

  • 实体模型包含大量数据字段,导致单个数据库表中存在大量字段。 随着系统迭代,可能会产生大量废弃字段。 尝试通过文档维护复杂的数据库模式通常不可行,因为维护文档的一致性本身就是一项高成本工程。
  • 存在多层次的复杂结构化数据(如struct、map、数组等),不易直接映射到数据库表字段。 即使采用诸如JSON 等新数据类型,也不能完全解决这个问题。
  • 随着业务变化或者对产品认知的改变,领域模型需要持续迭代。这就需要数据库表结构随之不断变更。 同时,还需要兼容旧数据,可能需要在线迁移旧数据。整个过程中可能需要实现零停机服务。

分层抽象解决方案

为了解决传统SQL数据库持久化方式的复杂度膨胀问题。我们需要进一步的抽象,将问题进行分层。 引入分层架构的思想,将领域层模型和持久化层进行隔离。

在领域层模型中,数据结构是为了方便领域逻辑而设计的,不考虑持久化需求。 在持久化层中,模型按存储需求、查询需求进行建模设计,不考虑领域逻辑的需求。

为了降低复杂度,可以接受数据重复存储。 要抓住主要矛盾,即大多数情况下主要问题是复杂度控制,而非存储性能。

针对存储层的需求,可以进行如下抽象化处理:

  1. 实体可以按实体ID进行全量存储和加载。
  2. 满足业务的查询过滤需求。通过特定数据字段进行过滤以找到实体 ID。 只有用于过滤的数据字段才需要单独的数据库表字段。
  3. 针对特定场景的数据查询性能优化。可以通过冗余存储的查询表来实现。

因此,我们可以采用以下方法来解决这些问题:

  1. 将领域模型整体进行序列化,然后以 blob、binary、json 等类型存储到单个字段中。

    columntype
    idstring
    payloadbinary
  2. 设计用于过滤的表。示例如下:

    columntype
    idstring
    user_idstring
    namestring
    created_atdatetime
    updated_atdatetime

    当需要支持过滤的字段不多时,以上两个表可以合并。随着业务需求的变化,还可以新增独立的过滤表,避免对旧表结构进行更改。

  3. 面向某些性能敏感且需要优化的查询场景,设计专门的查询表。 查询表仅包含特定查询所需的过滤字段和需要获取的数据字段。 甚至可以使用非 SQL数据库,如宽列数据库或全文索引数据库。 查询表的更新可以是同步或异步的,根据一致性需求、限制条件和开发复杂度进行权衡。